/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.flexengine.server;

import com.flexengine.logger.Logger;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Luann R. Athayde
 * @version 1.0
 * @since 1.0
 */
public class FlexEngineServer implements Runnable {

    public static final long MAX_USERS_CONNECTION = 4096;
    public static final int  DEFAULT_PORT         = 13000;
    // -- yyyy mm dd
    public static final long SERVER_VERSION       = 20131015;
    //--------------------------------------------------------------------------
    private Thread serverThread;
    private boolean running;
    //--------------------------------------------------------------------------
    private ServerSocket serverSocket;
    private Socket       clientSock;
    private int          serverPort;
    private boolean      canStart;
    private long         maxUsers;
    
    private final Map<Long, FlexEngineSession> sessions;
    
    //--------------------------------------------------------------------------
    private ServerListener listener;
    //--------------------------------------------------------------------------

    private FlexEngineServer() {
        serverThread = null;
        running = false;
        serverSocket = null;
        serverPort = DEFAULT_PORT;
        canStart = false;
        maxUsers = MAX_USERS_CONNECTION;
        sessions = new HashMap<>();
        listener = null;
    }

    public void setMaxUsers(long maxUsers) {
        if( maxUsers>0 && maxUsers<=MAX_USERS_CONNECTION ) {
            this.maxUsers = maxUsers;
        }
    }

    public void setServerPort(int serverPort) {
        if( serverPort>0 && serverPort<65535 ) {
            this.serverPort = serverPort;
        }
    }

    public int getServerPort() {
        return serverPort;
    }
    
    public long getMaxUsers() {
        return maxUsers;
    }

    public boolean initialize() {
        return initialize(serverPort, maxUsers);
    }

    public boolean initialize(int port) {
        return initialize(port, maxUsers);
    }
    public boolean initialize(long maxUsers) {
        return initialize(serverPort, maxUsers);
    }

    public boolean initialize(int port, long maxUsers) {
        if (serverSocket != null && serverSocket.isBound()) {
            return false;
        }
        if (maxUsers <= 0 || maxUsers > MAX_USERS_CONNECTION) {
            maxUsers = MAX_USERS_CONNECTION;
        }
        try {
            Logger.logMessage(getClass().getSimpleName() + " - initialize(): Initializing server on port = " + port + " with max users = " + maxUsers);
            this.serverPort = port;
            this.maxUsers = maxUsers;
            this.serverSocket = new ServerSocket(port);
            Logger.logMessage(getClass().getSimpleName() + " - initialize(): Server socket created...");
            if (serverSocket.isClosed()) {
                Logger.logWarning(getClass().getSimpleName() + " - initialize(" + port + "," + maxUsers + "): Server socket was closed...");
                canStart = false;
                return false;
            }
            canStart = true;
        } catch (IOException e) {
            canStart = false;
            serverSocket = null;
            Logger.logError(getClass().getSimpleName() + " - initialize(" + port + "," + maxUsers + "): " + e);
        }
        return canStart;
    }

    public void start() {
        if (canStart) {
            if (!running) {
                initialize();
                for( Long key : sessions.keySet() ) {
                    FlexEngineSession session = sessions.get(key);
                    session.disconnect();
                }
                sessions.clear();
                if (serverThread != null) {
                    running = false;
                    serverThread.interrupt();
                    serverThread = null;
                }
                running = true;
                serverThread = new Thread(this, getClass().getSimpleName());
                serverThread.start();
            } else {
                Logger.logWarning(getClass().getSimpleName() + " - start(" + serverPort + "," + maxUsers + "): Server is already running...");
            }
        } else {
            Logger.logWarning(getClass().getSimpleName() + " - start(" + serverPort + "," + maxUsers + "): Server can not be started...\n\t"
                    + "Please, be sure that initialize() method has been called propertily!");
        }
    }

    public void stop() {
        try {
            running = false;
            for( Long key : sessions.keySet() ) {
                FlexEngineSession session = sessions.get(key);
                session.disconnect();
            }
            sessions.clear();
            if (serverSocket != null) {
                serverSocket.close();
            }
            serverSocket = null;
            serverThread = null;
            Logger.logMessage(getClass().getSimpleName() + " - stop(): Server stoped...");
        } catch (IOException e) {
            Logger.logWarning(getClass().getSimpleName() + " - stop(): " + e);
        }
    }

    @Override
    public void run() {
        while (running) {
            try {
                Logger.logMessage(getClass().getSimpleName() + " - run(): Waiting for connection...");
                clientSock = serverSocket.accept();
                Logger.logMessage(getClass().getSimpleName() + " - run(): Connection received...");
                Thread createSession = new Thread(new CreateSession(clientSock), "CreateSession");
                createSession.setDaemon(true);
                createSession.start();
            } catch (IOException e) {
                Logger.logError(getClass().getSimpleName() + " - run(): " + e);
            }
        }
        Logger.logMessage(getClass().getSimpleName() + " - run(): Server finished...");
    }

    public void setListener(ServerListener listener) {
        this.listener = listener;
    }

    public ServerListener getListener() {
        return listener;
    }

    public FlexEngineSession getSession(long id) {
        return sessions.get(id);
    }
    
    public Map<Long, FlexEngineSession> getSessions() {
        return sessions;
    }

    public boolean isRunning() {
        return running;
    }
    
    private static FlexEngineServer instance;
    public static FlexEngineServer getInstance() {
        if (instance == null) {
            instance = new FlexEngineServer();
        }
        return instance;
    }
    
    private class CreateSession implements Runnable {

        private final Socket socket;

        public CreateSession(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            Logger.logMessage("CreateSession - run(): Creating session...");
            try {
                if (sessions.size() < maxUsers && listener!=null ) {
                    FlexEngineSession session = listener.getSession(socket);
                    FlexEngineStream stream = session.getStream();
                    Logger.logMessage("CreateSession - run(): Waiting for version...");
                    FlexMessage msg = stream.<FlexMessage>read();
                    Logger.logMessage("CreateSession - run(): msg = "+msg);
                    if( msg.getAction()==FlexEngineServer.SERVER_VERSION && msg.getMessage().equals("version") ) {
                        Logger.logMessage("CreateSession - run(): VERSION OK!");
                        msg = new FlexMessage(FlexEngineServer.SERVER_VERSION, "VERSION_OK");
                        stream.write(msg);
                    } else {
                        msg = new FlexMessage(FlexEngineServer.SERVER_VERSION, "WRONG_VERSION");
                        Logger.logMessage("CreateSession - run(): WRONG VERSION!");
                        stream.write(msg);
                        session.disconnect();
                    }
                    // -- session ID sanity CHECK!
                    if( session.getSessionID()<=0 ) {
                        session.setSessionID(1);
                    }
                    while( true ) {
                        if( sessions.containsKey(session.getSessionID()) ) {
                            session.setSessionID(session.getSessionID()+1);
                        } else {
                            break;
                        }
                    }
                    sessions.put(session.getSessionID(), session);
                    session.start();
                    
                    // -- listener for end session...
                    Logger.logMessage("FlexEngineServer - Starting the session listener...");
                    Logger.logMessage("FlexEngineServer - session running = "+session.isRunning());
                    while( session.isRunning() ) {
                        //Logger.logMessage("FlexEngineServer - Session ["+session.getSessionID()+"] connected = "+session.isConnected());
                        sleep(1000);
                    }
                    Logger.logMessage("FlexEngineServer - Removing session["+sessions.remove(session.getSessionID())+"]...");
                }
                else if( listener==null ) {
                    Logger.logMessage(getClass().getSimpleName() + " - run(): Listener not found!");
                    FlexMessage msg = new FlexMessage(0, "NOT_HAVE_LISTENER");
                    FlexEngineStream stream = new FlexEngineStream(
                            new ObjectInputStream(socket.getInputStream()),
                            new ObjectOutputStream(socket.getOutputStream()));
                    // -- receive version
                    stream.read();
                    // -- send info
                    stream.write(msg);
                    stream.close();
                    socket.close();
                }
                else {
                    Logger.logMessage(getClass().getSimpleName() + " - run(): The mas users has reached...");
                    FlexMessage msg = new FlexMessage(sessions.size(), "MAX_USERS_REACHED");
                    FlexEngineStream stream = new FlexEngineStream(
                            new ObjectInputStream(socket.getInputStream()),
                            new ObjectOutputStream(socket.getOutputStream()));
                    // -- receive version
                    stream.read();
                    // -- send info
                    stream.write(msg);
                    stream.close();
                    socket.close();
                }
                Logger.logMessage("CreateSession - run(): Create session fineshed...");
            } catch(Exception e) {
                Logger.logError(getClass()+" - run(): "+e);
            }
        }
        
        public void sleep(long milis) throws Exception {
            Thread.sleep(milis);
        }
        
    }
    
}
